import Module from './Module';
import $ from 'jquery';

class UndoManager extends Module {
	_init() {
		let redoShortcut;
		let undoShortcut;
		this.editor = this._module;
		this._stack = [];
		if (this.editor.util.os.mac) {
			undoShortcut = 'cmd+z';
			redoShortcut = 'shift+cmd+z';
		} else if (this.editor.util.os.win) {
			undoShortcut = 'ctrl+z';
			redoShortcut = 'ctrl+y';
		} else {
			undoShortcut = 'ctrl+z';
			redoShortcut = 'shift+ctrl+z';
		}
		this.editor.hotkeys.add(undoShortcut, ((_this => e => {
			e.preventDefault();
			_this.undo();
			return false;
		}))(this));
		this.editor.hotkeys.add(redoShortcut, ((_this => e => {
			e.preventDefault();
			_this.redo();
			return false;
		}))(this));
		this.throttledPushState = this.editor.util.throttle(((_this => () => _this._pushUndoState()))(this), 2000);
		this.editor.on('valuechanged', ((_this => (e, src) => {
			if (src === 'undo' || src === 'redo') {
				return;
			}
			return _this.throttledPushState();
		}))(this));
		this.editor.on('selectionchanged', ((_this => () => {
			_this.resetCaretPosition();
			return _this.update();
		}))(this));
		this.editor.on('focus', ((_this => () => {
			if (_this._stack.length === 0) {
				return _this._pushUndoState();
			}
		}))(this));
		return this.editor.on('blur', ((_this => e => _this.resetCaretPosition()))(this));
	}

	resetCaretPosition() {
		this._startPosition = null;
		return this._endPosition = null;
	}

	startPosition() {
		if (this.editor.selection._range) {
			this._startPosition || (this._startPosition = this._getPosition('start'));
		}
		return this._startPosition;
	}

	endPosition() {
		if (this.editor.selection._range) {
			this._endPosition || (this._endPosition = ((_this => () => {
				let range;
				range = _this.editor.selection.range();
				if (range.collapsed) {
					return _this._startPosition;
				}
				return _this._getPosition('end');
			}))(this)());
		}
		return this._endPosition;
	}

	_pushUndoState() {
		let caret;
		if (this.editor.triggerHandler('pushundostate') === false) {
			return;
		}
		caret = this.caretPosition();
		if (!caret.start) {
			return;
		}
		this._index += 1;
		this._stack.length = this._index;
		this._stack.push({
			html: this.editor.body.html(),
			caret: this.caretPosition()
		});
		if (this._stack.length > this._capacity) {
			this._stack.shift();
			return this._index -= 1;
		}
	}

	currentState() {
		if (this._stack.length && this._index > -1) {
			return this._stack[this._index];
		} else {
			return null;
		}
	}

	undo() {
		let state;
		if (this._index < 1 || this._stack.length < 2) {
			return;
		}
		this.editor.hidePopover();
		this._index -= 1;
		state = this._stack[this._index];
		this.editor.body.get(0).innerHTML = state.html;
		this.caretPosition(state.caret);
		this.editor.body.find('.selected').removeClass('selected');
		this.editor.sync();
		return this.editor.trigger('valuechanged', ['undo']);
	}

	redo() {
		let state;
		if (this._index < 0 || this._stack.length < this._index + 2) {
			return;
		}
		this.editor.hidePopover();
		this._index += 1;
		state = this._stack[this._index];
		this.editor.body.get(0).innerHTML = state.html;
		this.caretPosition(state.caret);
		this.editor.body.find('.selected').removeClass('selected');
		this.editor.sync();
		return this.editor.trigger('valuechanged', ['redo']);
	}

	update() {
		let currentState;
		currentState = this.currentState();
		if (!currentState) {
			return;
		}
		currentState.html = this.editor.body.html();
		return currentState.caret = this.caretPosition();
	}

	_getNodeOffset(node, index) {
		let $parent;
		let merging;
		let offset;
		if ($.isNumeric(index)) {
			$parent = $(node);
		} else {
			$parent = $(node).parent();
		}
		offset = 0;
		merging = false;
		$parent.contents().each((i, child) => {
			if (node === child || (index === i && i === 0)) {
				return false;
			}
			if (child.nodeType === Node.TEXT_NODE) {
				if (!merging && child.nodeValue.length > 0) {
					offset += 1;
					merging = true;
				}
			} else {
				offset += 1;
				merging = false;
			}
			if (index - 1 === i) {
				return false;
			}
			return null;
		});
		return offset;
	}

	_getPosition(type) {
		let $nodes;
		let node;
		let nodes;
		let offset;
		let position;
		let prevNode;
		let range;
		if (type == null) {
			type = 'start';
		}
		range = this.editor.selection.range();
		offset = range[`${type}Offset`];
		$nodes = this.editor.selection[`${type}Nodes`]();
		node = $nodes.first()[0];
		if (node.nodeType === Node.TEXT_NODE) {
			prevNode = node.previousSibling;
			while (prevNode && prevNode.nodeType === Node.TEXT_NODE) {
				node = prevNode;
				offset += this.editor.util.getNodeLength(prevNode);
				prevNode = prevNode.previousSibling;
			}
			nodes = $nodes.get();
			nodes[0] = node;
			$nodes = $(nodes);
		} else {
			offset = this._getNodeOffset(node, offset);
		}
		position = [offset];
		$nodes.each(((_this => (i, node) => position.unshift(_this._getNodeOffset(node))))(this));
		return position;
	}

	_getNodeByPosition(position) {
		let child;
		let childNodes;
		let i;
		let k;
		let len;
		let node;
		let offset;
		let ref;
		node = this.editor.body[0];
		ref = position.slice(0, position.length - 1);
		for (i = k = 0, len = ref.length; k < len; i = ++k) {
			offset = ref[i];
			childNodes = node.childNodes;
			if (offset > childNodes.length - 1) {
				if (i === position.length - 2 && $(node).is('pre:empty')) {
					child = document.createTextNode('');
					node.appendChild(child);
					childNodes = node.childNodes;
				} else {
					node = null;
					break;
				}
			}
			node = childNodes[offset];
		}
		return node;
	}

	caretPosition(caret) {
		let endContainer;
		let endOffset;
		let range;
		let startContainer;
		let startOffset;
		if (!caret) {
			range = this.editor.selection.range();
			caret = this.editor.inputManager.focused && (range != null) ? {
				start: this.startPosition(),
				end: this.endPosition(),
				collapsed: range.collapsed
			} : {};
			return caret;
		} else {
			if (!caret.start) {
				return;
			}
			startContainer = this._getNodeByPosition(caret.start);
			startOffset = caret.start[caret.start.length - 1];
			if (caret.collapsed) {
				endContainer = startContainer;
				endOffset = startOffset;
			} else {
				endContainer = this._getNodeByPosition(caret.end);
				endOffset = caret.start[caret.start.length - 1];
			}
			if (!startContainer || !endContainer) {
				if (typeof console !== "undefined" && console !== null) {
					if (typeof console.warn === "function") {
						console.warn('simditor: invalid caret state');
					}
				}
				return;
			}
			range = document.createRange();
			range.setStart(startContainer, startOffset);
			range.setEnd(endContainer, endOffset);
			return this.editor.selection.range(range);
		}
	}
}

UndoManager.pluginName = 'UndoManager';
UndoManager.prototype._index = -1;
UndoManager.prototype._capacity = 20;
UndoManager.prototype._startPosition = null;
UndoManager.prototype._endPosition = null;

export default UndoManager;